package com.atguigu.tingshu.common.Cache;

import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class MyCacheAspect {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;

    @SneakyThrows
    @Around("@annotation(com.atguigu.tingshu.common.Cache.MyCache)")
    public Object cacheAspect(ProceedingJoinPoint joinPoint) {
        //获取业务方法上的前缀  MethodSignature方法
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        MyCache myCache = signature.getMethod().getAnnotation(MyCache.class);
        String prefix = myCache.prefix();

        //获取参数 拼接上pre前缀，充当redis的key
        Object[] args = joinPoint.getArgs();
        String key = prefix + (Arrays.asList(args).toString());
        Object obj = redisTemplate.opsForValue().get(key);

        //如果没有访问mysql，并将专辑详情添加到缓存
        if (obj == null) {
            try {
                //生成锁的key和锁
                RLock rLock = redissonClient.getLock(key + ":lock");

                //有lock、trylock、空的lock  参数是过期时间和时间单位trylock多了个等待时间
                boolean tryLock = rLock.tryLock(10, 30, TimeUnit.SECONDS);
                if (tryLock) {
                    try {
                        //进入方法
                        Object proceed = joinPoint.proceed(args);
                        if (proceed == null) {
                            proceed = new AlbumInfo();
                        }
                        // 商品如果在数据库中不存在那么会缓存一个空对象进去，但是这个对象是没有用的，所以这个对象的过期时间应该不能太长，
                        // 如果太长会占用内存。 封装了个常量类，缓存60分钟
                        redisTemplate.opsForValue().set(key, proceed, RedisConstant.ALBUM_TIMEOUT, TimeUnit.SECONDS);
                        return proceed;

                        //最后无论如何需要解锁，就算报异常也解锁，所以使用try finally实现解锁
                    } finally {
                        rLock.unlock();
                    }
                } else {
                    //没抢到锁就一直抢 自旋
                    return cacheAspect(joinPoint);
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        } else {
            return obj;
        }
    }

}
